﻿using System.IO;
using NewLife.Data;
using NewLife.Log;

namespace NewLife.Net.Proxy;

/// <summary>代理会话。客户端的一次转发请求（或者Tcp连接），就是一个会话。转发的全部操作都在会话中完成。</summary>
/// <remarks>
/// 一个会话应该包含两端，两个Socket，服务端和客户端
/// 客户端<see cref="INetSession.Session"/>发来的数据，在这里经过一系列过滤器后，转发给服务端<see cref="RemoteServer"/>；
/// 服务端<see cref="RemoteServer"/>返回的数据，在这里经过过滤器后，转发给客户端<see cref="INetSession.Session"/>。
/// </remarks>
public class ProxySession : NetSession
{
    #region 属性
    /// <summary>主机</summary>
    public ProxyBase Host { get; set; }

    /// <summary>远程服务端。跟目标服务端通讯的那个Socket，其实是客户端TcpSession/UdpServer</summary>
    public ISocketClient RemoteServer { get; set; }

    /// <summary>服务端地址</summary>
    public NetUri RemoteServerUri { get; set; } = new NetUri();

    /// <summary>是否中转空数据包。默认true</summary>
    public Boolean ExchangeEmptyData { get; set; } = true;

    FileStream _logClient;
    FileStream _logServer;
    #endregion

    #region 构造
    /// <summary>实例化一个代理会话</summary>
    public ProxySession() { }

    /// <summary>子类重载实现资源释放逻辑时必须首先调用基类方法</summary>
    /// <param name="disposing">从Dispose调用（释放所有资源）还是析构函数调用（释放非托管资源）</param>
    protected override void Dispose(Boolean disposing)
    {
        base.Dispose(disposing);

        var remote = RemoteServer;
        if (remote != null)
        {
            RemoteServer = null;
            remote.TryDispose();
        }

        _logClient.TryDispose();
        _logServer.TryDispose();
    }
    #endregion

    #region 数据交换
    void WriteLogFile(String kind, Packet pk)
    {
        if (!Host.Debug) return;
        if (pk == null || pk.Total == 0) return;

        if (kind == "client")
        {
            if (_logClient == null)
            {
                var time = DateTime.Now;
                var path = XTrace.LogPath;
                var fileClient = path.CombinePath($"{time:yyyyMMddHHmmss}_{ID}_client.bin");
                _logClient = fileClient.AsFile().OpenWrite();
            }
            pk.CopyTo(_logClient);
        }
        else if (kind == "server")
        {
            if (_logServer == null)
            {
                var time = DateTime.Now;
                var path = XTrace.LogPath;
                var fileClient = path.CombinePath($"{time:yyyyMMddHHmmss}_{ID}_server.bin");
                _logServer = fileClient.AsFile().OpenWrite();
            }
            pk.CopyTo(_logServer);
        }
    }

    /// <summary>开始会话处理。</summary>
    public override void Start()
    {
        // 如果未指定远程协议，则与来源协议一致
        if (RemoteServerUri.Type == 0) RemoteServerUri.Type = Session.Local.Type;

        // 如果是Tcp，收到空数据时不要断开。为了稳定可靠，默认设置
        if (Session is TcpSession tcp) tcp.DisconnectWhenEmptyData = false;

        if (Host is ProxyBase proxy && proxy.ConnectRemoteOnStart) ConnectRemote(null);

        base.Start();
    }

    /// <summary>收到客户端发来的数据</summary>
    /// <param name="e"></param>
    protected override void OnReceive(ReceivedEventArgs e)
    {
        if (Disposed) return;

        var len = e.Packet.Total;
        if (len > 0 || len == 0 && ExchangeEmptyData)
        {
            using var span = Host.Tracer?.NewSpan("proxy:OnReceive", Remote + "");
            if (len > 0) WriteLogFile("client", e.Packet);

            // 如果未建立到远程服务器链接，则建立
            if (RemoteServer == null) ConnectRemote(e);

            // 如果已存在到远程服务器的链接，则把数据发向远程服务器
            if (RemoteServer != null) SendToRemote(e.Packet);
        }
    }

    /// <summary>开始远程连接</summary>
    /// <param name="e"></param>
    protected virtual void ConnectRemote(ReceivedEventArgs e)
    {
        if (RemoteServer != null) return;
        lock (this)
        {
            if (RemoteServer != null) return;

            using var span = Host.Tracer?.NewSpan("proxy:ConnectRemote", RemoteServerUri + "");

            var start = DateTime.Now;
            ISocketClient session = null;
            try
            {
                //WriteDebugLog("连接远程服务器 {0} 解析 {1}", RemoteServerUri, RemoteServerUri.Address);

                session = CreateRemote(e);
                //session.Log = Log;
                // Socket日志一致
                session.Log = Session.Log;
                session.OnDisposed += (s, e2) =>
                {
                    // 这个必须清空，是否需要保持会话由OnRemoteDispose决定
                    RemoteServer = null;
                    OnRemoteDispose(s as ISocketClient);
                };
                session.Received += Remote_Received;
                session.Open();

                //WriteDebugLog("连接远程服务器成功");

                RemoteServer = session;
            }
            catch (Exception ex)
            {
                var ts = DateTime.Now - start;
                //WriteError("无法为{0}连接远程服务器{1}！耗时{2}！{3}", Remote, RemoteServerUri, ts, ex.Message);
                WriteError(ex.Message);

                if (session != null) session.Dispose();
                Dispose();
            }
        }
    }

    /// <summary>为会话创建与远程服务器通讯的Socket。可以使用Socket池达到重用的目的。</summary>
    /// <param name="e"></param>
    /// <returns></returns>
    protected virtual ISocketClient CreateRemote(ReceivedEventArgs e)
    {
        var client = RemoteServerUri.CreateRemote();
        // 如果是Tcp，收到空数据时不要断开。为了稳定可靠，默认设置
        if (client is TcpSession tcp) tcp.DisconnectWhenEmptyData = false;

        return client;
    }

    /// <summary>远程连接断开时触发。默认销毁整个会话，子类可根据业务情况决定客户端与代理的链接是否重用。</summary>
    /// <param name="client"></param>
    protected virtual void OnRemoteDispose(ISocketClient client) => Dispose();

    void Remote_Received(Object sender, ReceivedEventArgs e)
    {
        if (Disposed) return;

        try
        {
            OnReceiveRemote(e);
        }
        catch (Exception ex)
        {
            WriteError(ex.Message);
            Dispose();
        }
    }

    /// <summary>收到远程服务器返回的数据</summary>
    /// <param name="e"></param>
    protected virtual void OnReceiveRemote(ReceivedEventArgs e)
    {
        if (Disposed) return;

        var len = e.Packet.Total;
        if (len > 0 || len == 0 && ExchangeEmptyData)
        {
            using var span = Host.Tracer?.NewSpan("proxy:OnReceiveRemote", RemoteServerUri + "");
            if (len > 0) WriteLogFile("server", e.Packet);

            var session = Session;
            if (session == null || session.Disposed)
                Dispose();
            else
                Send(e.Packet);
        }
    }
    #endregion

    #region 发送
    /// <summary>发送数据</summary>
    /// <param name="pk">缓冲区</param>
    public virtual Int32 SendToRemote(Packet pk)
    {
        var client = RemoteServer;
        using var span = Host.Tracer?.NewSpan($"proxy:SendToRemote:{client.Local.Address}", RemoteServerUri + "");
        try
        {
            return RemoteServer.Send(pk);
        }
        catch (Exception ex)
        {
            span?.SetError(ex, null);

            Dispose();
            throw;
        }
    }
    #endregion

    #region 错误处理
    /// <summary></summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected override void OnError(Object sender, ExceptionEventArgs e)
    {
        if (e.Exception != null) Dispose();
    }
    #endregion

    #region 辅助
    private String _LogPrefix;
    /// <summary>日志前缀</summary>
    public override String LogPrefix
    {
        get
        {
            if (_LogPrefix == null)
            {
                var session = this as INetSession;
                var name = session.Host == null ? "" : session.Host.Name.TrimEnd("Proxy");
                _LogPrefix = $"{name}[{ID}] ";
            }
            return _LogPrefix;
        }
        set { _LogPrefix = value; }
    }

    /// <summary>已重载。</summary>
    /// <returns></returns>
    public override String ToString() => base.ToString() + "=>" + RemoteServerUri;
    #endregion
}